﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using Megaplan.API.Extentions;
using Megaplan.API.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Megaplan.API
{
    public class MegaplanClient
    {
        public const string Domain = "megaplan.ru";
        private readonly bool debugMode;
        private readonly string host;

        public MegaplanClient(string subdomain, bool debugMode = false)
        {
            this.debugMode = debugMode;

            if (subdomain == null)
                throw new ArgumentNullException("subdomain");

            string knownHost = subdomain + "." + Domain;

            if (UriHostNameType.Dns == Uri.CheckHostName(knownHost))
            {
                host = knownHost;
            }
            else
            {
                throw new ArgumentException("Недопустимое имя домена третьего уровня", "subdomain");
            }
        }

        /// <summary>
        /// ID доступа для формирования сигнатуры запросов
        /// </summary>
        public string AccessId { get; private set; }

        /// <summary>
        /// Секретный ключ для формирования сигнатуры запросов
        /// </summary>
        public string SecretKey { get; private set; }

        public string GetBaseUri(bool useSSL)
        {
            if (useSSL || !debugMode)
            {
                return "https://" + host;
            }
            return "http://" + host;
        }

        private HttpWebRequest CreateWebRequest(string endPoint, bool isSign)
        {
            var request = (HttpWebRequest)WebRequest.Create(endPoint);

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            if (isSign)
            {
                SignRequest(request);
            }

            return request;
        }



        private string GetXAuthHeader(string signature, string accessId, string secretKey)
        {
            var cryptoProvider = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey));
            byte[] encriptedBytes = cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(signature));
            string encryptedString = encriptedBytes.Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
            return accessId + ":" + Convert.ToBase64String(Encoding.UTF8.GetBytes(encryptedString));
        }

        private void SignRequest(HttpWebRequest request)
        {
            DateTime requestDate = DateTime.UtcNow;
            string requestDateRfc = requestDate.ToString("r", CultureInfo.InvariantCulture);
            string signhost = request.Address.Host + request.Address.LocalPath;
            string signature = string.Join("\n",
                                           new[] { request.Method, "", request.ContentType, requestDateRfc, signhost });
            string xAutHeader = GetXAuthHeader(signature, AccessId, SecretKey);

            request.Headers.Add("Accept-Encoding", "identity");
            request.Accept = "application/json";
            request.UserAgent = "SdfApi_Request";
            request.Date = requestDate;
            request.Headers.Add("X-Authorization", xAutHeader);
            request.KeepAlive = false;
        }

        /// <summary>
        /// Авторизация
        /// </summary>
        /// <param name="login">Логин пользователя</param>
        /// <param name="pass">Пароль пользователя</param>
        /// <returns></returns>
        public bool Authorize(string login, string pass)
        {
            string pasHash = Md5.GetMd5Hash(pass);

            return AuthorizePassHash(login, pasHash);
        }

        /// <summary>
        /// Авторизация
        /// </summary>
        /// <param name="login">Логин пользователя</param>
        /// <param name="pasHash">Хешь MD5 пароля пользователя</param>
        /// <returns></returns>
        public bool AuthorizePassHash(string login, string pasHash)
        {
            string endPoint = GetBaseUri(true) +
                              string.Format("/BumsCommonApiV01/User/authorize.api?Login={0}&Password={1}", login,
                                            pasHash);

            HttpWebRequest request = CreateWebRequest(endPoint, false);

            string content = Execute(request, null);

            try
            {
                JObject jObject = JObject.Parse(content);
                if (jObject.SelectToken("status.code").ToString() == "ok")
                {
                    AccessId = jObject.SelectToken("data.AccessId").AsString();
                    SecretKey = jObject.SelectToken("data.SecretKey").AsString();
                    return true;
                }
            }
            catch (Exception)
            {
                throw new Exception(content);
            }

            return false;
        }

        public string Execute(HttpWebRequest request, byte[] postData)
        {
            try
            {
                if (null != postData)
                {
                    request.ContentLength = postData.Length;
                    using (Stream dataStream = request.GetRequestStream())
                    {
                        dataStream.Write(postData, 0, postData.Length);
                    }
                }

                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    string responseValue;

                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        string message = String.Format("POST failed. Received HTTP {0}", response.StatusCode);
                        throw new ApplicationException(message);
                    }

                    // grab the response  
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        using (var reader = new StreamReader(responseStream))
                        {
                            responseValue = reader.ReadToEnd();
                        }
                    }

                    return responseValue;
                }
            }
            catch (WebException exception)
            {
                if (exception.Status == WebExceptionStatus.ProtocolError)
                {
                    var response = exception.Response as HttpWebResponse;
                    if (response != null)
                    {
                        return response.StatusDescription;
                    }
                }

                throw;
            }
        }

        public DateTime GetServerTime()
        {
            string content = GetContentByUri("/BumsCommonApiV01/System/datetime.api");

            JObject jObject = JObject.Parse(content);

            string content2 = jObject.SelectToken("data.datetime").ToString();

            return DateTime.Parse(content2);
        }

        public IEnumerable<Task> GetTaskList()
        {
            string content = GetContentByUri("/BumsTaskApiV01/Task/list.api");

            JObject jObject = JObject.Parse(content);

            IList<JToken> list = jObject["data"]["tasks"].Children().ToList();

            return list.Select(token => JsonConvert.DeserializeObject<Task>(token.ToString())).ToList();
        }

        public IEnumerable<Task> GetEmployeeTaskList(int employeeId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("EmployeeId=" + employeeId);

            string content = GetContentByUri("/BumsTaskApiV01/Task/list.api", parameters);

            JObject jObject = JObject.Parse(content);

            IList<JToken> list = jObject["data"]["tasks"].Children().ToList();

            return list.Select(token => JsonConvert.DeserializeObject<Task>(token.ToString())).ToList();
        }

        public IEnumerable<Department> GetDepartmentList()
        {
            string content = GetContentByUri("/BumsStaffApiV01/Department/list.api");

            JObject jObject = JObject.Parse(content);

            IList<JToken> list = jObject["data"]["departments"].Children().ToList();

            return list.Select(token => JsonConvert.DeserializeObject<Department>(token.ToString())).ToList();
        }

        public IEnumerable<Employee> GetEmployeesList()
        {
            byte[] parameters = Encoding.UTF8.GetBytes("Status=all");

            string content = GetContentByUri("/BumsStaffApiV01/Employee/list.api", parameters);

            JObject jObject = JObject.Parse(content);

            foreach (JToken value in jObject["data"]["employees"])
            {
                //if (value.Parent == jdata.Last)
                //{
                //    break;
                //}

                var item = JsonConvert.DeserializeObject<Employee>(value.ToString());
                yield return item;
            }
        }

        public IEnumerable<Comment> GetCommentsList(int taskId)
        {
            IEnumerable<JToken> list = GetCommentsTokenList(taskId);

            return list.Select(token => JsonConvert.DeserializeObject<Comment>(token.ToString())).ToList();
        }

        public IEnumerable<JToken> GetCommentsTokenList(int taskId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("SubjectType=task&SubjectId=" + taskId + "&Order=asc");

            string content = GetContentByUri("/BumsCommonApiV01/Comment/list.api", parameters);

            if ("Access denied" == content)
            {
                throw new SecurityException();
            }

            JObject jObject = JObject.Parse(content);

            IList<JToken> list = jObject["data"]["comments"].Children().ToList();

            return list;
        }

        public EmployeeCard GetEmployeeCard(int employeeId)
        {
            JToken token = GetEmployeeCardToken(employeeId);

            return JsonConvert.DeserializeObject<EmployeeCard>(token.ToString());
        }

        public ProjectCard GetProjectCard(int projectId)
        {
            JToken token = GetPojectCardToken(projectId);

            return JsonConvert.DeserializeObject<ProjectCard>(token.ToString());
        }

        public TaskCard GetTaskCard(int taskId)
        {
            JToken token = GetTaskCardToken(taskId);

            return JsonConvert.DeserializeObject<TaskCard>(token.ToString());
        }

        public JToken GetEmployeeCardToken(int employeeId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("Id=" + employeeId);

            string content = GetContentByUri("/BumsStaffApiV01/Employee/card.api", parameters);

            JObject jObject = JObject.Parse(content);

            JToken token = jObject["data"]["employee"];

            return token;
        }

        public JToken GetPojectCardToken(int projectId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("Id=" + projectId);

            string content = GetContentByUri("/BumsProjectApiV01/Project/card.api", parameters);

            JObject jObject = JObject.Parse(content);

            JToken token = jObject["data"]["project"];

            return token;
        }

        public JToken GetTaskCardToken(int taskId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("Id=" + taskId);

            string content = GetContentByUri("/BumsTaskApiV01/Task/card.api", parameters);

            JObject jObject = JObject.Parse(content);

            JToken token = jObject["data"]["task"];

            return token;
        }

        public JToken GetAllTaskCardToken(int taskId)
        {
            byte[] parameters = Encoding.UTF8.GetBytes("Id=" + taskId);

            string content = GetContentByUri("/BumsTaskApiV01/Task/card.api", parameters);

            JObject jObject = JObject.Parse(content);

            JToken token = jObject["data"]["task"];

            return token;
        }

        private string GetContentByUri(string serviceUri, byte[] parameters = null)
        {
            string endPoint = GetBaseUri(false) + serviceUri;
            HttpWebRequest request = CreateWebRequest(endPoint, true);
            string content = Execute(request, parameters);
            return content;
        }

        public IEnumerable<JToken> GetActualCommentsTokenList()
        {
            string content = GetContentByUri("/BumsCommonApiV01/Comment/all.api");

            if ("Access denied" == content)
            {
                throw new SecurityException();
            }

            JObject jObject = JObject.Parse(content);

            IList<JToken> list = jObject["data"]["comments"].Children().ToList();

            return list;
        }

        public TaskBase CreateTask(TaskModel task)
        {
            JToken token = CreateTaskToken(task);

            return JsonConvert.DeserializeObject<TaskBase>(token.ToString());
        }

        protected JToken CreateTaskToken(TaskModel task)
        {
            byte[] parameters = Encoding.UTF8.GetBytes(task.GetQueryString("Model[{0}]={1}"));

            string content = GetContentByUri("/BumsTaskApiV01/Task/create.api", parameters);

            JObject jObject = JObject.Parse(content);

            return jObject["data"]["task"];
        }

        public CommentBase CreateComment(SubjectType subjectType, int subjectId, CommentModel task)
        {
            JToken token = CreateCommentToken(subjectType, subjectId, task);

            return JsonConvert.DeserializeObject<CommentBase>(token.ToString());
        }

        protected JToken CreateCommentToken(SubjectType subjectType, int subjectId, CommentModel task)
        {
            byte[] parameters = Encoding.UTF8.GetBytes(String.Format("SubjectType={0}&SubjectId={1}&{2}", subjectType.ToString().ToLower(), subjectId, task.GetQueryString("Model[{0}]={1}")));

            string content = GetContentByUri("/BumsCommonApiV01/Comment/create.api", parameters);

            JObject jObject = JObject.Parse(content);

            return jObject["data"]["comment"];
        }
    }
}